import sys
import os
import json
from collections import defaultdict
from shapely.geometry import Polygon, LineString
from geopy.distance import great_circle

# 添加上级目录到Python路径，使其能够导入主目录中的模块
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# 导入主目录中的文件读写接口
from read_filedata import read_planedata, write_actiondata

# --- 全局配置加载 ---
CONFIG = {}
try:
    # 修改读取配置文件的路径，指向上级目录
    config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config.json')
    with open(config_path, 'r', encoding='utf-8') as f:
        CONFIG = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
    print("警告: 'config.json' 文件未找到或格式无效，将使用默认阈值。")

# 从配置中获取阈值，提供默认值
# 信号
STOP_ADSB_TIME = CONFIG.get('signal', {}).get('stopAdsbTime', 150)

# 从plane命名空间获取配置参数
PLANE_CONFIG = CONFIG.get('plane', {})

# 高度
ALT_HIGH = PLANE_CONFIG.get('altitude', {}).get('high', 25000)
ALT_MID = PLANE_CONFIG.get('altitude', {}).get('mid', 10000)
ALT_LOW = PLANE_CONFIG.get('altitude', {}).get('low', 1000)
TAKEOFF_LANDING_GROUND_FT = PLANE_CONFIG.get('altitude', {}).get('takeoff_landing_ground', 10)
TAKEOFF_LANDING_FLIGHT_FT = PLANE_CONFIG.get('altitude', {}).get('takeoff_landing_flight', 50)

# 运动
V_RATE_FPM = PLANE_CONFIG.get('movement', {}).get('vertical_rate_fpm', 500)
ACCEL_MPS2 = PLANE_CONFIG.get('movement', {}).get('acceleration_mps2', 0.3)

# 速度
SUPERSONIC_MPS = PLANE_CONFIG.get('speed', {}).get('supersonic_mps', 343)

# 区域
PROJECTION_KM = PLANE_CONFIG.get('region', {}).get('projection_distance_km', 100)
PROJECTION_STEP_KM = PLANE_CONFIG.get('region', {}).get('projection_sample_step_km', 5)
APPROACH_MERGE_SECONDS = PLANE_CONFIG.get('region', {}).get('approach_departure_merge_gap_seconds', 120)

# 工作模式
WM_GRID_SIZE_METERS = PLANE_CONFIG.get('working_mode', {}).get('grid_size_meters', 500)
WM_TIME_THRESHOLD_SECONDS = PLANE_CONFIG.get('working_mode', {}).get('time_threshold_seconds', 1800)
WM_TIME_MIN_THRESHOLD_SECONDS = PLANE_CONFIG.get('working_mode', {}).get('TIME_MIN_THRESHOLD_SECONDS', 480)
WM_ANGLE_MIN = PLANE_CONFIG.get('working_mode', {}).get('angle_min', 135)
WM_ANGLE_MAX = PLANE_CONFIG.get('working_mode', {}).get('angle_max', 225)
WM_MERGE_THRESHOLD_SECONDS = PLANE_CONFIG.get('working_mode', {}).get('merge_threshold_seconds', 900)
WM_MIN_DURATION_SECONDS = PLANE_CONFIG.get('working_mode', {}).get('min_duration_seconds', 60)
WM_ADAPTIVE_GRID_MIN_POINTS = PLANE_CONFIG.get('working_mode', {}).get('adaptive_grid_min_points', 2)
WM_ADAPTIVE_GRID_MAX_RADIUS = PLANE_CONFIG.get('working_mode', {}).get('adaptive_grid_max_radius', 13)

# 盘旋模式
CM_GRID_SIZE_METERS = PLANE_CONFIG.get('circling_mode', {}).get('grid_size_meters', 500)
CM_TIME_THRESHOLD_SECONDS = PLANE_CONFIG.get('circling_mode', {}).get('time_threshold_seconds', 1800)
CM_ANGLE_MAX = PLANE_CONFIG.get('circling_mode', {}).get('angle_max', 45)
CM_MIN_VISITS = PLANE_CONFIG.get('circling_mode', {}).get('min_visits', 3)
CM_MERGE_THRESHOLD_SECONDS = PLANE_CONFIG.get('circling_mode', {}).get('merge_threshold_seconds', 900)
CM_MIN_DURATION_SECONDS = PLANE_CONFIG.get('circling_mode', {}).get('min_duration_seconds', 60)

# 修改区域文件读取路径
def get_area_file_path():
    """获取区域数据文件的绝对路径"""
    return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data', 'importPlace_port.json')


def check_close_adsb(data_input, now_timestamp):
    data_output = []
    for data in data_input[0:100]:
        if not data['trackData']:
            continue
        latest_data = data['trackData'][-1]
        latest_timestamp = latest_data['timestamp']
        if now_timestamp - latest_timestamp >= STOP_ADSB_TIME:
            data_output.append({
                'trackId': data['trackId'],
                'actionKey': '1A',
                'startTime': latest_timestamp,
                'endTime': 0
            })
    return data_output

# 根据高度返回对应的状态码
def get_altitude_state(altitude):
    """根据高度返回对应的状态码"""
    if altitude > ALT_HIGH:
        return '2C'  # 高空飞行
    elif ALT_MID < altitude <= ALT_HIGH:
        return '2D'  # 中空飞行
    elif ALT_LOW < altitude <= ALT_MID:
        return '2E'  # 低空飞行
    elif 0 < altitude <= ALT_LOW:
        return '2F'  # 超低空飞行
    return None

# 检测不同高度分层的飞行状态
def check_altitude_states(data_input, now_timestamp):
    data_output = []
    for data in data_input:
        track_points = sorted(data['trackData'], key=lambda p: p['timestamp'])
        if not track_points:
            continue

        active_event = None
        for point in track_points:
            point_state = get_altitude_state(point['altitude'])

            if active_event and point_state == active_event['actionKey']:
                active_event['endTime'] = point['timestamp']
            else:
                if active_event:
                    data_output.append(active_event)
                
                if point_state is not None:
                    active_event = {
                        'trackId': data['trackId'],
                        'actionKey': point_state,
                        'startTime': point['timestamp'],
                        'endTime': point['timestamp']
                    }
                else:
                    active_event = None
        
        if active_event:
            data_output.append(active_event)
            
    return data_output

# 检测ADSB信号重新打开
def check_open_adsb(data_input, now_timestamp):
    data_output = []
    for data in data_input:
        # 轨迹点必须按时间排序，才能正确计算间隔
        track_points = sorted(data['trackData'], key=lambda p: p['timestamp'])
        
        # 至少需要两个点来判断时间间隔
        if len(track_points) < 2:
            continue
        
        # 遍历轨迹点，寻找时间间隔过大的点
        for i in range(1, len(track_points)):
            previous_point = track_points[i-1]
            current_point = track_points[i]
            
            # 如果两个连续点的时间间隔大于150秒，则认为ADSB信号重新打开
            if current_point['timestamp'] - previous_point['timestamp'] >= STOP_ADSB_TIME:
                data_output.append({
                    'trackId': data['trackId'],
                    'actionKey': '1B',
                    'startTime': current_point['timestamp'],
                    'endTime': 0
                })
    return data_output

# 检测爬升和下降飞行
def check_vertical_movement(data_input, now_timestamp):
    data_output = []
    CLIMB_RATE_THRESHOLD_FPM = V_RATE_FPM  
    DESCEND_RATE_THRESHOLD_FPM = -V_RATE_FPM

    for data in data_input:
        track_points = sorted(data['trackData'], key=lambda p: p['timestamp'])
        if len(track_points) < 2:
            continue

        active_event = None
        for i in range(1, len(track_points)):
            prev_point = track_points[i-1]
            curr_point = track_points[i]

            delta_altitude = curr_point['altitude'] - prev_point['altitude']
            delta_time_seconds = curr_point['timestamp'] - prev_point['timestamp']

            if delta_time_seconds <= 0:
                continue

            vertical_rate_fpm = (delta_altitude / delta_time_seconds) * 60

            current_state = None
            if vertical_rate_fpm > CLIMB_RATE_THRESHOLD_FPM:
                current_state = '2G'
            elif vertical_rate_fpm < DESCEND_RATE_THRESHOLD_FPM:
                current_state = '2H'

            if active_event and current_state == active_event['actionKey']:
                active_event['endTime'] = curr_point['timestamp']
            else:
                if active_event:
                    data_output.append(active_event)
                
                if current_state is not None:
                    active_event = {
                        'trackId': data['trackId'],
                        'actionKey': current_state,
                        'startTime': prev_point['timestamp'],
                        'endTime': curr_point['timestamp']
                    }
                else:
                    active_event = None
        
        if active_event:
            data_output.append(active_event)
            
    return data_output

# 检测加速和减速飞行
def check_speed_change(data_input, now_timestamp):
    data_output = []
    ACCEL_THRESHOLD_MPS2 = ACCEL_MPS2  
    DECEL_THRESHOLD_MPS2 = -ACCEL_MPS2 

    for data in data_input:
        track_points = sorted(data['trackData'], key=lambda p: p['timestamp'])
        if len(track_points) < 2:
            continue

        active_event = None
        for i in range(1, len(track_points)):
            prev_point = track_points[i-1]
            curr_point = track_points[i]

            delta_speed = curr_point.get('speed', 0.0) - prev_point.get('speed', 0.0)
            delta_time_seconds = curr_point['timestamp'] - prev_point['timestamp']

            if delta_time_seconds <= 0:
                continue

            acceleration_mps2 = delta_speed / delta_time_seconds

            current_state = None
            if acceleration_mps2 > ACCEL_THRESHOLD_MPS2:
                current_state = '2J'
            elif acceleration_mps2 < DECEL_THRESHOLD_MPS2:
                current_state = '2K'

            if active_event and current_state == active_event['actionKey']:
                active_event['endTime'] = curr_point['timestamp']
            else:
                if active_event:
                    data_output.append(active_event)
                
                if current_state is not None:
                    active_event = {
                        'trackId': data['trackId'],
                        'actionKey': current_state,
                        'startTime': prev_point['timestamp'],
                        'endTime': curr_point['timestamp']
                    }
                else:
                    active_event = None
        
        if active_event:
            data_output.append(active_event)
            
    return data_output

# 检测超音速飞行
def check_supersonic_flight(data_input, now_timestamp):
    data_output = []
    SPEED_OF_SOUND_MPS = SUPERSONIC_MPS

    for data in data_input:
        track_points = sorted(data['trackData'], key=lambda p: p['timestamp'])
        if not track_points:
            continue

        active_event = None
        for point in track_points:
            speed = point.get('speed', 0.0)
            is_supersonic = speed > SPEED_OF_SOUND_MPS

            if active_event and is_supersonic:
                active_event['endTime'] = point['timestamp']
            else:
                if active_event:
                    data_output.append(active_event)
                
                if is_supersonic:
                    active_event = {
                        'trackId': data['trackId'],
                        'actionKey': '2L',
                        'startTime': point['timestamp'],
                        'endTime': point['timestamp']
                    }
                else:
                    active_event = None
        
        if active_event:
            data_output.append(active_event)
            
    return data_output

# 检测起飞和降落阶段
def check_takeoff_landing(data_input, now_timestamp):
    data_output = []
    GROUND_ALTITUDE_THRESHOLD = TAKEOFF_LANDING_GROUND_FT
    FLIGHT_ALTITUDE_THRESHOLD = TAKEOFF_LANDING_FLIGHT_FT

    for data in data_input:
        track_points = sorted(data['trackData'], key=lambda p: p['timestamp'])
        if len(track_points) < 2:
            continue

        i = 0
        while i < len(track_points):
            # 起飞逻辑
            # 条件1：找到地面滑行起点（在地面且速度>0）
            if point_is_on_ground(track_points[i]) and track_points[i].get('speed', 0) > 0:
                # 条件2：检查是否为滑行段的开始点
                is_start_of_roll = (i == 0) or \
                                   (point_is_on_ground(track_points[i-1]) and track_points[i-1].get('speed', 0) == 0)

                if is_start_of_roll:
                    takeoff_start_time = track_points[i]['timestamp']
                    
                    # 查找地面滑行结束和爬升开始的点
                    lift_off_index = -1
                    for j in range(i, len(track_points)):
                        if not point_is_on_ground(track_points[j]):
                            lift_off_index = j
                            break
                    
                    # 如果找到离地点，查找起飞阶段结束点（达到50英尺）
                    if lift_off_index != -1:
                        takeoff_end_time = -1
                        for k in range(lift_off_index, len(track_points)):
                            if track_points[k]['altitude'] >= FLIGHT_ALTITUDE_THRESHOLD:
                                takeoff_end_time = track_points[k]['timestamp']
                                data_output.append({
                                    'trackId': data['trackId'],
                                    'actionKey': '2A',
                                    'startTime': takeoff_start_time,
                                    'endTime': takeoff_end_time
                                })
                                i = k # 将主循环指针移过此段
                                break
                        if takeoff_end_time != -1: continue

            # 降落逻辑
            # 条件1：找到降落进近起点（穿越50英尺阈值且下降中）
            if i > 0 and track_points[i-1]['altitude'] > FLIGHT_ALTITUDE_THRESHOLD and \
               track_points[i]['altitude'] <= FLIGHT_ALTITUDE_THRESHOLD and \
               track_points[i]['altitude'] < track_points[i-1]['altitude']:
                
                landing_start_time = track_points[i-1]['timestamp']

                # 查找接地点
                touchdown_index = -1
                for j in range(i, len(track_points)):
                    if point_is_on_ground(track_points[j]):
                        touchdown_index = j
                        break
                
                # 如果找到接地点，查找降落滑行结束点（速度为0或状态改变）
                if touchdown_index != -1:
                    landing_end_time = -1
                    for k in range(touchdown_index, len(track_points)):
                        # 降落滑行结束条件：速度为零或飞机不再在地面
                        if not point_is_on_ground(track_points[k]) or track_points[k].get('speed', 0) == 0:
                            landing_end_time = track_points[k]['timestamp']
                            data_output.append({
                                'trackId': data['trackId'],
                                'actionKey': '2B',
                                'startTime': landing_start_time,
                                'endTime': landing_end_time
                            })
                            i = k # 将主循环指针移过此段
                            break
                    
                    # 如果滑行到数据末尾，将最后一点标记为结束
                    if landing_end_time == -1:
                        landing_end_time = track_points[-1]['timestamp']
                        data_output.append({
                            'trackId': data['trackId'],
                            'actionKey': '2B',
                            'startTime': landing_start_time,
                            'endTime': landing_end_time
                        })
                        i = len(track_points) # 结束循环
                        continue
                    
                    if landing_end_time != -1: continue

            i += 1
            
    return data_output

# 判断点是否在地面
def point_is_on_ground(point):
    """Helper to check if a point is on the ground."""
    GROUND_ALTITUDE_THRESHOLD = TAKEOFF_LANDING_GROUND_FT  # 英尺
    return point.get('on_ground', False) or point.get('altitude', 100) <= GROUND_ALTITUDE_THRESHOLD

# 检测地面驻停状态
def check_ground_hold(data_input, now_timestamp):
    data_output = []
    for data in data_input:
        track_points = sorted(data['trackData'], key=lambda p: p['timestamp'])
        if not track_points:
            continue

        active_event = None
        for point in track_points:
            is_ground_hold = point_is_on_ground(point) and point.get('speed', -1) == 0

            if active_event and is_ground_hold:
                active_event['endTime'] = point['timestamp']
            else:
                if active_event:
                    data_output.append(active_event)
                
                if is_ground_hold:
                    active_event = {
                        'trackId': data['trackId'],
                        'actionKey': '3A',
                        'startTime': point['timestamp'],
                        'endTime': point['timestamp']
                    }
                else:
                    active_event = None
        
        if active_event:
            data_output.append(active_event)
            
    return data_output

# 检测进入重要区域
def check_region_approach_departure(data_input, now_timestamp):
    data_output = []
    PROJECTION_DISTANCE_KM = PROJECTION_KM  # 投影距离100公里

    # 读取区域数据并转换为Shapely多边形
    try:
        area_file_path = get_area_file_path()
        with open(area_file_path, 'r', encoding='utf-8') as f:
            regions_data = json.load(f)
        
        important_areas = []
        raw_areas_for_point_check = regions_data.get("ImportArea", [])
        for area_data in raw_areas_for_point_check:
            min_lon, min_lat, max_lon, max_lat = area_data['areaPos']
            important_areas.append({
                'name': area_data['regionName'],
                'geom': Polygon([(min_lon, min_lat), (max_lon, min_lat), (max_lon, max_lat), (min_lon, max_lat)])
            })

    except (FileNotFoundError, json.JSONDecodeError):
        print(f"警告: '{area_file_path}' 文件未找到或格式无效，跳过飞向/飞离区域检测。")
        return []

    # 辅助：判断点是否在任意重要区域内
    def point_is_in_any_region(lat, lon):
        for area in raw_areas_for_point_check:
            min_lon, min_lat, max_lon, max_lat = area['areaPos']
            if min_lon <= lon <= max_lon and min_lat <= lat <= max_lat:
                return True
        return False

    for data in data_input:
        track_points = sorted(data['trackData'], key=lambda p: p['timestamp'])
        if len(track_points) < 2:
            continue
            
        # 预先计算航向角 
        for i in range(len(track_points) - 1):
            p1, p2 = track_points[i], track_points[i+1]
            if p1['latitude'] != p2['latitude'] or p1['longitude'] != p2['longitude']:
                 track_points[i]['heading'] = calculate_bearing(p1['latitude'], p1['longitude'], p2['latitude'], p2['longitude'])
            else:
                 track_points[i]['heading'] = track_points[i-1].get('heading', 0) if i > 0 else 0
        if len(track_points) > 1:
            track_points[-1]['heading'] = track_points[-2].get('heading', 0)

        towards_events = []
        away_events = []

        for point in track_points:
            # 如果点已在区域内，则跳过，不判断飞向/飞离
            if point_is_in_any_region(point['latitude'], point['longitude']):
                continue

            heading = point.get('heading')
            if heading is None:
                continue
            
            start_lon, start_lat = point['longitude'], point['latitude']

            # --- 使用 Shapely 和 Geopy 进行高效相交检测 ---

            # 正向投影（飞向）
            end_point_fwd = great_circle(kilometers=PROJECTION_DISTANCE_KM).destination((start_lat, start_lon), heading)
            line_fwd = LineString([(start_lon, start_lat), (end_point_fwd.longitude, end_point_fwd.latitude)])
            for area in important_areas:
                if line_fwd.intersects(area['geom']):
                    towards_events.append({'timestamp': point['timestamp'], 'regionName': area['name']})
            
            # 反向投影（飞离）
            backward_heading = (heading - 180 + 360) % 360
            end_point_bwd = great_circle(kilometers=PROJECTION_DISTANCE_KM).destination((start_lat, start_lon), backward_heading)
            line_bwd = LineString([(start_lon, start_lat), (end_point_bwd.longitude, end_point_bwd.latitude)])
            for area in important_areas:
                 if line_bwd.intersects(area['geom']):
                    away_events.append({'timestamp': point['timestamp'], 'regionName': area['name']})

        # 合并连续事件
        all_event_types = [('3P', towards_events), ('3Q', away_events)]
        for action_key, events in all_event_types:
            events_by_region = defaultdict(list)
            for ev in events:
                events_by_region[ev['regionName']].append(ev['timestamp'])

            for region_name, timestamps in events_by_region.items():
                if not timestamps: continue
                
                # 按时间戳排序以正确合并
                timestamps.sort()
                
                merged = []
                if not timestamps: continue 
                merged.append({'startTime': timestamps[0], 'endTime': timestamps[0]})

                for i in range(1, len(timestamps)):
                    if timestamps[i] - merged[-1]['endTime'] < APPROACH_MERGE_SECONDS:
                        merged[-1]['endTime'] = timestamps[i]
                    else:
                        merged.append({'startTime': timestamps[i], 'endTime': timestamps[i]})

                for segment in merged:
                    data_output.append({
                        'trackId': data['trackId'],
                        'actionKey': action_key,
                        'startTime': segment['startTime'],
                        'endTime': segment['endTime'],
                        'regionName': region_name
                    })

    return data_output


def check_region_entry(data_input, now_timestamp):
    data_output = []
    
    # 读取区域数据
    try:
        area_file_path = get_area_file_path()
        with open(area_file_path, 'r', encoding='utf-8') as f:
            regions_data = json.load(f)
        important_areas = regions_data.get("ImportArea", [])
    except (FileNotFoundError, json.JSONDecodeError):
        print(f"警告: '{area_file_path}' 文件未找到或格式无效，跳过区域进入检测。")
        return []

    def get_region_for_point(lat, lon):
        for area in important_areas:
            min_lon, min_lat, max_lon, max_lat = area['areaPos']
            if min_lon <= lon <= max_lon and min_lat <= lat <= max_lat:
                return area['regionName']
        return None

    for data in data_input:
        track_points = sorted(data['trackData'], key=lambda p: p['timestamp'])
        if not track_points:
            continue

        active_event = None
        for point in track_points:
            region_name = get_region_for_point(point['latitude'], point['longitude'])

            if active_event and region_name == active_event.get('regionName'):
                active_event['endTime'] = point['timestamp']
            else:
                if active_event:
                    data_output.append(active_event)
                
                if region_name is not None:
                    active_event = {
                        'trackId': data['trackId'],
                        'actionKey': '3R',
                        'startTime': point['timestamp'],
                        'endTime': point['timestamp'],
                        'regionName': region_name
                    }
                else:
                    active_event = None
        
        if active_event:
            data_output.append(active_event)
            
    return data_output

# ================== 自适应工作模式检测相关代码 ==================

import pandas as pd
import numpy as np
from collections import namedtuple
from dataclasses import dataclass
import matplotlib.pyplot as plt
import os
import itertools


@dataclass
class Passage:
    """Represents a single passage through a grid cell."""
    entry_timestamp: float
    exit_timestamp: float
    entry_heading: float
    exit_heading: float

def calculate_bearing(lat1, lon1, lat2, lon2):
    """
    Calculates the initial bearing (forward azimuth) from point 1 to point 2.
    """
    # 将角度转换为弧度
    lat1_rad = np.radians(lat1)
    lon1_rad = np.radians(lon1)
    lat2_rad = np.radians(lat2)
    lon2_rad = np.radians(lon2)

    dLon = lon2_rad - lon1_rad

    y = np.sin(dLon) * np.cos(lat2_rad)
    x = np.cos(lat1_rad) * np.sin(lat2_rad) - np.sin(lat1_rad) * np.cos(lat2_rad) * np.cos(dLon)
    
    bearing = np.arctan2(y, x)
    bearing = np.degrees(bearing)
    bearing = (bearing + 360) % 360 # 归一化到0-360
    return bearing

def _calculate_angle_diff(h1, h2):
    """Calculates the absolute difference between two headings."""
    diff = abs(h1 - h2)
    return min(diff, 360 - diff)

def _get_grid_id(lat, lon, avg_lat, grid_size_meters):
    """Calculates a grid ID for a given lat/lon."""
    meters_per_degree_lon = 111320.0 * np.cos(np.radians(avg_lat))
    grid_x = int(lon * meters_per_degree_lon / grid_size_meters)
    grid_y = int(lat * 111132.0 / grid_size_meters)
    return (grid_x, grid_y)

def _get_surrounding_grid_ids(grid_id, radius):
    """Gets all grid IDs in a square radius around a central grid ID."""
    x, y = grid_id
    ids = set()
    for r_x in range(-radius, radius + 1):
        for r_y in range(-radius, radius + 1):
            ids.add((x + r_x, y + r_y))
    return ids

def _identify_consecutive_segments(sorted_indices):
    """Splits sorted point indices into segments of consecutive points."""
    if not sorted_indices:
        return []
    segments = []
    current_segment = [sorted_indices[0]]
    for i in range(1, len(sorted_indices)):
        if sorted_indices[i] - sorted_indices[i-1] <= 2: # 允许一个点缺失
            current_segment.append(sorted_indices[i])
        else:
            segments.append(current_segment)
            current_segment = [sorted_indices[i]]
    if current_segment:
        segments.append(current_segment)
    return segments

def _merge_segments(segments, merge_gap_seconds):
    """Merges overlapping or adjacent time segments."""
    if not segments:
        return []
    segments.sort(key=lambda x: x[0])
    merged = [segments[0]]
    for current_start, current_end in segments[1:]:
        last_start, last_end = merged[-1]
        if current_start <= last_end + merge_gap_seconds:
            merged[-1] = (last_start, max(last_end, current_end))
        else:
            merged.append((current_start, current_end))
    return merged

def check_working_mode(data_input, now_timestamp, exclusion_segments=None):
    """
    使用自适应网格方法分析飞行轨迹以检测工作模式
    """
    # --- 配置参数（来自modules/config.py） ---
    GRID_SIZE_METERS = WM_GRID_SIZE_METERS
    TIME_THRESHOLD_SECONDS = WM_TIME_THRESHOLD_SECONDS
    TIME_MIN_THRESHOLD_SECONDS = WM_TIME_MIN_THRESHOLD_SECONDS
    ANGLE_MIN = WM_ANGLE_MIN
    ANGLE_MAX = WM_ANGLE_MAX
    MERGE_THRESHOLD_SECONDS = WM_MERGE_THRESHOLD_SECONDS
    MIN_DURATION_SECONDS = WM_MIN_DURATION_SECONDS
    ADAPTIVE_GRID_MIN_POINTS = WM_ADAPTIVE_GRID_MIN_POINTS
    ADAPTIVE_GRID_MAX_RADIUS = WM_ADAPTIVE_GRID_MAX_RADIUS
    
    all_results = []

    # 处理排除时间段以便高效查找
    exclusion_by_track = defaultdict(list)
    if exclusion_segments:
        for seg in exclusion_segments:
            exclusion_by_track[seg['trackId']].append((seg['startTime'], seg['endTime']))

    for track_data in data_input:
        track_id = track_data.get('trackId')
        points = track_data.get('trackData', [])

        # 根据排除时间段过滤点
        track_exclusions = exclusion_by_track.get(track_id)
        if track_exclusions:
            filtered_points = []
            for p in points:
                is_excluded = False
                for start_ts, end_ts in track_exclusions:
                    if start_ts <= p['timestamp'] <= end_ts:
                        is_excluded = True
                        break
                if not is_excluded:
                    filtered_points.append(p)
            points = filtered_points

        if len(points) < 5:
            continue

        # 1. 转换为DataFrame并准备数据
        df = pd.DataFrame(points).rename(columns={
            'timestamp': 'Timestamp', 'longitude': 'Longitude', 'latitude': 'Latitude', 
            'altitude': 'Altitude', 'speed': 'Speed'
        }).sort_values('Timestamp').reset_index(drop=True)
        
        # 计算航向
        df['Direction'] = 0.0
        for i in range(len(df) - 1):
            p1, p2 = df.iloc[i], df.iloc[i+1]
            if p1['Latitude'] == p2['Latitude'] and p1['Longitude'] == p2['Longitude']:
                 df.loc[i, 'Direction'] = df.loc[i-1, 'Direction'] if i > 0 else 0
            else:
                 df.loc[i, 'Direction'] = calculate_bearing(p1['Latitude'], p1['Longitude'], p2['Latitude'], p2['Longitude'])
        if len(df) > 1:
            df.loc[len(df)-1, 'Direction'] = df.loc[len(df)-2, 'Direction']
        
        trajectory_df = df
        avg_latitude = trajectory_df['Latitude'].mean()

        # 2. 自适应网格分析
        point_to_grid_map = {idx: _get_grid_id(row['Latitude'], row['Longitude'], avg_latitude, GRID_SIZE_METERS) 
                             for idx, row in trajectory_df.iterrows()}
        
        grid_points = defaultdict(list)
        for point_idx, grid_id in point_to_grid_map.items():
            grid_points[grid_id].append(point_idx)

        final_grid_map = {}
        processed_grids = set()
        for grid_id, point_indices in grid_points.items():
            if grid_id in processed_grids:
                continue
            
            segments = _identify_consecutive_segments(sorted(point_indices))
            has_small_segment = any(len(seg) < ADAPTIVE_GRID_MIN_POINTS for seg in segments)

            if not has_small_segment:
                final_grid_map[grid_id] = grid_id
                processed_grids.add(grid_id)
                continue

            # 尝试合并
            merged_successfully = False
            for radius in range(1, ADAPTIVE_GRID_MAX_RADIUS + 1):
                surrounding_ids = _get_surrounding_grid_ids(grid_id, radius)
                all_points_in_zone = list(itertools.chain.from_iterable(
                    grid_points.get(sg_id, []) for sg_id in surrounding_ids
                ))
                
                if not all_points_in_zone: continue

                merged_segments = _identify_consecutive_segments(sorted(all_points_in_zone))
                if all(len(seg) >= ADAPTIVE_GRID_MIN_POINTS for seg in merged_segments):
                    # 成功，将所有周围网格映射到这个中心网格
                    for sg_id in surrounding_ids:
                        if sg_id in grid_points:
                            final_grid_map[sg_id] = grid_id
                            processed_grids.add(sg_id)
                    merged_successfully = True
                    break
            
            if not merged_successfully:
                final_grid_map[grid_id] = grid_id
                processed_grids.add(grid_id)

        # 3. 从最终（合并的）网格生成访问日志
        visit_log = defaultdict(list)
        trajectory_df['final_grid_id'] = trajectory_df.index.map(lambda i: final_grid_map.get(point_to_grid_map.get(i)))
        
        trajectory_df['grid_change'] = trajectory_df['final_grid_id'].ne(trajectory_df['final_grid_id'].shift()).cumsum()
        passages_df = trajectory_df.dropna(subset=['final_grid_id'])

        for _, group in passages_df.groupby('grid_change'):
            if len(group) < 1: continue
            
            final_id = group['final_grid_id'].iloc[0]
            entry_row = group.iloc[0]
            exit_row = group.iloc[-1]
            
            visit_log[final_id].append(Passage(
                entry_timestamp=entry_row['Timestamp'],
                exit_timestamp=exit_row['Timestamp'],
                entry_heading=entry_row['Direction'],
                exit_heading=exit_row['Direction']
            ))

        # 4. 从访问记录中查找工作段（重入逻辑）
        working_segments = []
        for _, passages in visit_log.items():
            if len(passages) < 2: continue

            for i in range(len(passages)):
                for j in range(i + 1, len(passages)):
                    p1, p2 = passages[i], passages[j]
                    
                    time_diff = abs(p2.entry_timestamp - p1.entry_timestamp)
                    angle_diff = _calculate_angle_diff(p1.entry_heading, p2.entry_heading)

                    is_reentry = TIME_MIN_THRESHOLD_SECONDS < time_diff < TIME_THRESHOLD_SECONDS
                    is_opposite = ANGLE_MIN <= angle_diff <= ANGLE_MAX
                    
                    if is_reentry and is_opposite:
                        working_segments.append((p1.entry_timestamp, p1.exit_timestamp))
                        working_segments.append((p2.entry_timestamp, p2.exit_timestamp))

        # 5. 合并段并格式化输出
        merged = _merge_segments(working_segments, MERGE_THRESHOLD_SECONDS)
        
        final_segments = []
        for start_ts, end_ts in merged:
            if end_ts - start_ts >= MIN_DURATION_SECONDS:
                final_segments.append({
                    'trackId': track_id,
                    'actionKey': '3D',
                    'startTime': float(start_ts),
                    'endTime': float(end_ts)
                })
        
        all_results.extend(final_segments)

    return all_results

def visualize_working_modes(data_input, working_segments, circling_segments=None, output_dir='visualizations'):
    """
    可视化飞行轨迹并突出显示工作模式段和盘旋模式段。
    将每个航迹ID对应的一张PNG图像保存到指定的输出目录。   
    
    参数:
        data_input: 包含飞行轨迹数据的列表
        working_segments: 工作模式航段列表
        circling_segments: 盘旋模式航段列表
        output_dir: 输出目录路径
    """

    try:
        plt.rcParams['font.sans-serif'] = ['SimHei']  
        plt.rcParams['axes.unicode_minus'] = False  
    except Exception:
        print("警告：无法设置中文字体'SimHei'，标题和标签中的中文可能显示为方框。")
        print("请确保您的系统已安装'SimHei'字体或修改代码以使用其他可用的中文字体。")

    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
        print(f"创建可视化输出文件夹: {output_dir}")

    # 按trackId分组
    segments_by_track = defaultdict(list)
    for seg in working_segments:
        segments_by_track[seg['trackId']].append(('work', seg))
    
    if circling_segments:
        for seg in circling_segments:
            segments_by_track[seg['trackId']].append(('circle', seg))

    # 处理原始数据输入中的每个轨迹
    for track in data_input:
        track_id = track['trackId']
        
        # 仅当此轨迹有工作段或盘旋段时才生成可视化图
        if not track_id in segments_by_track:
            continue

        print(f"正在为飞行器 {track_id} 生成可视化图...")

        # 将轨迹数据转换为DataFrame便于处理和绘图
        df = pd.DataFrame(track['trackData'])
        if df.empty:
            continue

        # 创建一个新图
        plt.figure(figsize=(15, 10))
        
        # 1. 绘制完整轨迹
        plt.plot(df['longitude'], df['latitude'], color='blue', linestyle='-', marker='.', markersize=3, label='完整轨迹')

        # 2. 高亮所有工作段和盘旋段
        track_segments = segments_by_track[track_id]
        
        work_drawn = False
        circle_drawn = False
        
        for seg_type, segment in track_segments:
            start_ts = segment['startTime']
            end_ts = segment['endTime']
            
            # 过滤DataFrame以获取时间段内的点
            segment_df = df[(df['timestamp'] >= start_ts) & (df['timestamp'] <= end_ts)]
            
            if not segment_df.empty:
                if seg_type == 'work':
                    # 仅对第一个工作段使用标签，避免图例混乱
                    label = '工作模式航段' if not work_drawn else ''
                    plt.plot(segment_df['longitude'], segment_df['latitude'], color='red', linestyle='-', linewidth=2.5, marker='o', markersize=4, label=label)
                    work_drawn = True
                else:  # 盘旋模式
                    # 仅对第一个盘旋段使用标签，避免图例混乱
                    label = '盘旋模式航段' if not circle_drawn else ''
                    plt.plot(segment_df['longitude'], segment_df['latitude'], color='green', linestyle='-', linewidth=2.5, marker='o', markersize=4, label=label)
                    circle_drawn = True

        # 配置图表美观
        plt.title(f'飞行器 {track_id} 的轨迹与模式识别结果')
        plt.xlabel('经度 (Longitude)')
        plt.ylabel('纬度 (Latitude)')
        plt.legend()
        plt.grid(True)
        # 确保纵横比相等，避免地理路径失真
        plt.gca().set_aspect('equal', adjustable='box')

        # 保存图片
        output_path = os.path.join(output_dir, f'flight_modes_{track_id}.png')
        plt.savefig(output_path, bbox_inches='tight', dpi=150)
        plt.close() # 关闭图形以释放内存

        print(f"已保存可视化图: {output_path}")

# ================== 自适应工作模式检测相关代码 END ==================

def check_circling_mode(data_input, now_timestamp, exclusion_segments=None):
    """
    分析飞行轨迹以识别盘旋模式，使用与工作模式相似的网格方法。
    盘旋模式的特点是在短时间内多次同向访问同一网格。
    
    参数:
        data_input: 包含飞行轨迹数据的列表
        now_timestamp: 当前时间戳
        exclusion_segments: 需要排除的时间段列表（例如起飞、降落等）
        
    返回:
        盘旋模式航段列表，每个元素包含trackId、actionKey、startTime和endTime
    """
    # --- 配置参数 ---
    GRID_SIZE_METERS = CM_GRID_SIZE_METERS
    TIME_THRESHOLD_SECONDS = CM_TIME_THRESHOLD_SECONDS
    ANGLE_MAX = CM_ANGLE_MAX
    MIN_VISITS = CM_MIN_VISITS
    MERGE_THRESHOLD_SECONDS = CM_MERGE_THRESHOLD_SECONDS
    MIN_DURATION_SECONDS = CM_MIN_DURATION_SECONDS
    ADAPTIVE_GRID_MIN_POINTS = WM_ADAPTIVE_GRID_MIN_POINTS  # 复用工作模式的网格参数
    ADAPTIVE_GRID_MAX_RADIUS = WM_ADAPTIVE_GRID_MAX_RADIUS  # 复用工作模式的网格参数
    
    all_results = []

    # 处理排除段以便高效查找
    exclusion_by_track = defaultdict(list)
    if exclusion_segments:
        for seg in exclusion_segments:
            exclusion_by_track[seg['trackId']].append((seg['startTime'], seg['endTime']))

    for track_data in data_input:
        track_id = track_data.get('trackId')
        points = track_data.get('trackData', [])

        # 根据exclusion_segments过滤点
        track_exclusions = exclusion_by_track.get(track_id)
        if track_exclusions:
            filtered_points = []
            for p in points:
                is_excluded = False
                for start_ts, end_ts in track_exclusions:
                    if start_ts <= p['timestamp'] <= end_ts:
                        is_excluded = True
                        break
                if not is_excluded:
                    filtered_points.append(p)
            points = filtered_points

        if len(points) < 5:
            continue

        # 1. 转换为DataFrame并准备数据
        df = pd.DataFrame(points).rename(columns={
            'timestamp': 'Timestamp', 'longitude': 'Longitude', 'latitude': 'Latitude', 
            'altitude': 'Altitude', 'speed': 'Speed'
        }).sort_values('Timestamp').reset_index(drop=True)
        
        # 计算方向（航向）
        df['Direction'] = 0.0
        for i in range(len(df) - 1):
            p1, p2 = df.iloc[i], df.iloc[i+1]
            if p1['Latitude'] == p2['Latitude'] and p1['Longitude'] == p2['Longitude']:
                 df.loc[i, 'Direction'] = df.loc[i-1, 'Direction'] if i > 0 else 0
            else:
                 df.loc[i, 'Direction'] = calculate_bearing(p1['Latitude'], p1['Longitude'], p2['Latitude'], p2['Longitude'])
        if len(df) > 1:
            df.loc[len(df)-1, 'Direction'] = df.loc[len(df)-2, 'Direction']
        
        trajectory_df = df
        avg_latitude = trajectory_df['Latitude'].mean()

        # 2. 自适应网格分析
        point_to_grid_map = {idx: _get_grid_id(row['Latitude'], row['Longitude'], avg_latitude, GRID_SIZE_METERS) 
                             for idx, row in trajectory_df.iterrows()}
        
        grid_points = defaultdict(list)
        for point_idx, grid_id in point_to_grid_map.items():
            grid_points[grid_id].append(point_idx)

        final_grid_map = {}
        processed_grids = set()
        for grid_id, point_indices in grid_points.items():
            if grid_id in processed_grids:
                continue
            
            segments = _identify_consecutive_segments(sorted(point_indices))
            has_small_segment = any(len(seg) < ADAPTIVE_GRID_MIN_POINTS for seg in segments)

            if not has_small_segment:
                final_grid_map[grid_id] = grid_id
                processed_grids.add(grid_id)
                continue

            # 尝试合并
            merged_successfully = False
            for radius in range(1, ADAPTIVE_GRID_MAX_RADIUS + 1):
                surrounding_ids = _get_surrounding_grid_ids(grid_id, radius)
                all_points_in_zone = list(itertools.chain.from_iterable(
                    grid_points.get(sg_id, []) for sg_id in surrounding_ids
                ))
                
                if not all_points_in_zone: continue

                merged_segments = _identify_consecutive_segments(sorted(all_points_in_zone))
                if all(len(seg) >= ADAPTIVE_GRID_MIN_POINTS for seg in merged_segments):
                    # 成功，将所有周围网格映射到这个中心网格
                    for sg_id in surrounding_ids:
                        if sg_id in grid_points:
                            final_grid_map[sg_id] = grid_id
                            processed_grids.add(sg_id)
                    merged_successfully = True
                    break
            
            if not merged_successfully:
                final_grid_map[grid_id] = grid_id
                processed_grids.add(grid_id)

        # 3. 从最终（合并的）网格生成访问日志
        visit_log = defaultdict(list)
        trajectory_df['final_grid_id'] = trajectory_df.index.map(lambda i: final_grid_map.get(point_to_grid_map.get(i)))
        
        trajectory_df['grid_change'] = trajectory_df['final_grid_id'].ne(trajectory_df['final_grid_id'].shift()).cumsum()
        passages_df = trajectory_df.dropna(subset=['final_grid_id'])

        for _, group in passages_df.groupby('grid_change'):
            if len(group) < 1: continue
            
            final_id = group['final_grid_id'].iloc[0]
            entry_row = group.iloc[0]
            exit_row = group.iloc[-1]
            
            visit_log[final_id].append(Passage(
                entry_timestamp=entry_row['Timestamp'],
                exit_timestamp=exit_row['Timestamp'],
                entry_heading=entry_row['Direction'],
                exit_heading=exit_row['Direction']
            ))

        # 4. 从访问记录中查找盘旋段（同向多次访问逻辑）
        circling_segments = []
        for grid_id, passages in visit_log.items():
            if len(passages) < MIN_VISITS:
                continue
                
            # 按时间戳排序
            sorted_passages = sorted(passages, key=lambda p: p.entry_timestamp)
            
            # 检查连续的同向访问
            for i in range(len(sorted_passages) - MIN_VISITS + 1):
                is_circling = True
                first_heading = sorted_passages[i].entry_heading
                start_time = sorted_passages[i].entry_timestamp
                end_time = sorted_passages[i + MIN_VISITS - 1].exit_timestamp
                
                # 检查时间约束
                if end_time - start_time > TIME_THRESHOLD_SECONDS:
                    continue
                
                # 检查所有连续访问的航向是否同向（与第一个航向的差值在阈值内）
                for j in range(i + 1, i + MIN_VISITS):
                    angle_diff = _calculate_angle_diff(first_heading, sorted_passages[j].entry_heading)
                    if angle_diff > ANGLE_MAX:
                        is_circling = False
                        break
                
                if is_circling:
                    circling_segments.append((start_time, end_time))

        # 5. 合并段并格式化输出
        merged = _merge_segments(circling_segments, MERGE_THRESHOLD_SECONDS)
        
        final_segments = []
        for start_ts, end_ts in merged:
            if end_ts - start_ts >= MIN_DURATION_SECONDS:
                final_segments.append({
                    'trackId': track_id,
                    'actionKey': '3E',  # 盘旋模式的动作代码
                    'startTime': float(start_ts),
                    'endTime': float(end_ts)
                })
        
        all_results.extend(final_segments)

    return all_results

# 添加主函数用于处理飞机行为识别
def process_plane_behavior(data_input=None, now_timestamp=None):
    """
    处理飞机行为识别
    
    参数:
        data_input: 飞机轨迹数据，如果为None则自动从文件读取
        now_timestamp: 当前时间戳，如果为None则使用读取数据时的时间戳
        
    返回:
        包含所有检测结果的字典
    """
    # 如果没有提供数据，从文件读取
    if data_input is None:
        data_input, now_timestamp = read_planedata()
        print(f'读取飞机数据: {len(data_input)} 条轨迹, 时间戳: {now_timestamp}')
    
    # 运行各种行为检测算法
    results = {}
    
    # 1. 检测信号开关行为
    results['close_adsb'] = check_close_adsb(data_input, now_timestamp)
    results['open_adsb'] = check_open_adsb(data_input, now_timestamp)
    
    # 2. 检测高度状态
    results['altitude_states'] = check_altitude_states(data_input, now_timestamp)
    
    # 3. 检测垂直运动
    results['vertical_movement'] = check_vertical_movement(data_input, now_timestamp)
    
    # 4. 检测速度变化
    results['speed_change'] = check_speed_change(data_input, now_timestamp)
    
    # 5. 检测超音速飞行
    results['supersonic_flight'] = check_supersonic_flight(data_input, now_timestamp)
    
    # 6. 检测起飞降落
    results['takeoff_landing'] = check_takeoff_landing(data_input, now_timestamp)
    
    # 7. 检测地面驻停
    results['ground_hold'] = check_ground_hold(data_input, now_timestamp)
    
    # 将起飞、降落和地面驻停的航段定义为排除区域
    exclusion_segments = results['takeoff_landing'] + results['ground_hold']
    
    # 8. 检测盘旋模式
    results['circling_mode'] = check_circling_mode(data_input, now_timestamp, exclusion_segments=exclusion_segments)
    
    # 将盘旋模式也加入排除区域，确保工作模式与盘旋模式互斥
    exclusion_segments = exclusion_segments + results['circling_mode']
    
    # 9. 检测工作模式
    results['working_mode'] = check_working_mode(data_input, now_timestamp, exclusion_segments=exclusion_segments)
    
    # 10. 检测进入区域
    results['region_entry'] = check_region_entry(data_input, now_timestamp)
    
    # 11. 检测飞向飞离区域
    results['region_approach_departure'] = check_region_approach_departure(data_input, now_timestamp)
    
    return results


def save_plane_behavior_results(results):
    """
    将行为识别结果保存到CSV文件
    
    参数:
        results: 行为检测结果字典
    """
    # 合并所有行为结果
    all_actions = []
    for action_list in results.values():
        all_actions.extend(action_list)
    
    # 使用主目录的写入函数保存结果
    if all_actions:
        write_actiondata(all_actions, is_ship=False)
        print(f'已写入 {len(all_actions)} 条飞机行为记录')


if __name__ == '__main__':
    # 从文件读取数据
    data_input, now_timestamp = read_planedata()
    print(f'读取飞机数据: {len(data_input)} 条轨迹')
    
    # 执行行为识别
    results = process_plane_behavior(data_input, now_timestamp)
    
    # 打印结果概要
    for behavior_type, behavior_data in results.items():
        print(f'{behavior_type}: 检测到 {len(behavior_data)} 条记录')
    
    # 保存结果
    save_plane_behavior_results(results)
    
    print('处理完成')

